from PySide6.QtGui import QFont, QFontDatabase, QIcon
from PySide6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                               QPushButton, QTabWidget, QTextEdit, QStatusBar,
                               QLabel, QApplication, QStyleFactory, QFileDialog)
from PySide6.QtCore import Qt, QThread, Signal, QObject, QTimer
import logging
import asyncio
import sys
import os
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from bleak import BleakScanner, BleakClient
import qasync
from volcenginesdkarkruntime import Ark
import textwrap

# 设置日志
logger = logging.getLogger(__name__)
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.StreamHandler()]
)


class TGAMDataCollector(QObject):
    data_received = Signal(dict)
    status_updated = Signal(str)
    file_created = Signal(str)
    connection_changed = Signal(bool)
    recording_changed = Signal(bool)

    def __init__(self):
        super().__init__()
        self.client = None
        self.buffer = bytearray()
        self.raw_file = None
        self.csv_file = None
        self.is_recording = False
        self.is_connected = False  # 使用 is_connected 而不是 connected
        self.last_data_time = time.time()
        self.current_csv_path = ""  # 添加当前文件路径属性

        # EEG频段定义
        self.EEG_BANDS = [
            'Delta', 'Theta',
            'Low Alpha', 'High Alpha',
            'Low Beta', 'High Beta',
            'Low Gamma', 'Middle Gamma'
        ]

    async def connect_device(self):
        """连接蓝牙设备"""
        try:
            self.status_updated.emit("正在扫描设备...")
            device = await BleakScanner.find_device_by_filter(
                lambda d, _: d.name and "JDY-18" in d.name
            )
            if not device:
                self.status_updated.emit("未找到兼容设备")
                return False

            self.status_updated.emit(f"找到设备: {device.name}")
            self.client = BleakClient(device)
            await self.client.connect(timeout=15)
            await self.client.start_notify("0000FFE1-0000-1000-8000-00805F9B34FB", self._data_handler)
            self.is_connected = True
            self.connection_changed.emit(True)
            self.status_updated.emit("设备已连接")
            return True
        except Exception as e:
            self.status_updated.emit(f"连接失败: {str(e)}")
            return False

    def _data_handler(self, sender, data):
        """数据处理回调"""
        self.buffer.extend(data)
        self._process_buffer()
        self.last_data_time = time.time()

    def _process_buffer(self):
        """处理缓冲区数据"""
        try:
            while len(self.buffer) >= 4:
                # 查找大包同步头 (AA AA 20)
                sync_pos = -1
                for i in range(len(self.buffer) - 2):
                    if self.buffer[i] == 0xAA and self.buffer[i + 1] == 0xAA and self.buffer[i + 2] == 0x20:
                        sync_pos = i
                        break

                if sync_pos == -1:
                    # 没有找到同步头，清空缓冲区
                    if len(self.buffer) > 100:
                        self.buffer.clear()
                    return

                if sync_pos > 0:
                    # 删除同步头前的无效数据
                    del self.buffer[:sync_pos]
                    continue

                if len(self.buffer) < 32:  # 等待完整数据包
                    return

                packet = bytes(self.buffer[:32])
                del self.buffer[:32]

                if self.raw_file is not None:
                    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                    self.raw_file.write(f"{timestamp}|{packet.hex()}\n")

                parsed = self._parse_packet(packet)
                if parsed:
                    self.data_received.emit(parsed)
                    if self.csv_file:
                        self._write_to_csv(parsed)
        except Exception as e:
            logger.error(f"处理数据包出错: {str(e)}")
            self.buffer.clear()

    def _parse_packet(self, packet):
        """解析TGAM数据包"""
        try:
            # 检查同步头和长度
            if len(packet) < 32 or packet[0] != 0xAA or packet[1] != 0xAA or packet[2] != 0x20:
                return None

            # 计算校验和 (从payload长度开始到倒数第二字节)
            payload_sum = sum(packet[2:31])
            checksum = ((payload_sum ^ 0xFFFFFFFF) & 0xFF)

            if checksum != packet[31]:
                logger.warning(f"校验和失败: 计算值={checksum}, 实际值={packet[31]}")
                return None

            # 解析信号质量 (0x02)
            signal_quality = packet[4]

            # 检查EEG功率开始标记 (0x83)
            if packet[5] != 0x83:
                logger.warning("缺少EEG功率开始标记")
                return None

            # 解析EEG功率谱 (24字节 = 8频段×3字节)
            eeg_power = {}
            index = 6
            for band in self.EEG_BANDS:
                # 每个频段3个字节 (高位->低位)
                value = (packet[index] << 16) | (packet[index + 1] << 8) | packet[index + 2]
                eeg_power[band] = value
                index += 3

            # 解析专注度 (0x04) 和放松度 (0x05)
            attention = 0
            meditation = 0
            while index < 30:  # 最后2字节是其他数据+校验和
                code = packet[index]
                if code == 0x04:
                    attention = packet[index + 1]
                    index += 2
                elif code == 0x05:
                    meditation = packet[index + 1]
                    index += 2
                else:
                    index += 1

            return {
                "时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
                "信号质量": signal_quality,
                "专注度": attention,
                "放松度": meditation,
                **eeg_power
            }
        except Exception as e:
            logger.error(f"解析数据包异常: {str(e)}")
            return None

    def _write_to_csv(self, data):
        """写入CSV文件"""
        try:
            if self.csv_file.tell() == 0:  # 新文件写入表头
                header = [
                    "时间", "信号质量", "专注度", "放松度",
                    *self.EEG_BANDS
                ]
                self.csv_file.write(",".join(header) + "\n")

            # 按顺序写入数据
            row = [
                data["时间"],
                str(data["信号质量"]),
                str(data["专注度"]),
                str(data["放松度"]),
                *[str(data[band]) for band in self.EEG_BANDS]
            ]
            self.csv_file.write(",".join(row) + "\n")
        except Exception as e:
            logger.error(f"写入CSV失败: {str(e)}")

    def start_recording(self, base_path):
        """开始记录数据"""
        try:
            csv_path = f"{base_path}_data.csv"
            self.csv_file = open(csv_path, "w", encoding="utf-8")
            logger.info(f"创建数据文件: {csv_path}")
            self.current_csv_path = csv_path  # 保存当前文件路径

            self.is_recording = True
            self.recording_changed.emit(True)
            self.file_created.emit(csv_path)
            self.status_updated.emit("开始记录数据...")
            return True
        except Exception as e:
            self.status_updated.emit(f"创建文件失败: {str(e)}")
            return False

    def stop_recording(self):
        """停止记录"""
        if self.csv_file:
            self.csv_file.close()
            self.csv_file = None
        self.is_recording = False
        self.recording_changed.emit(False)
        self.buffer.clear()
        self.status_updated.emit("数据记录已停止")

    async def disconnect_device(self):
        """断开设备连接"""
        if self.client and self.is_connected:
            try:
                await self.client.disconnect()
                self.status_updated.emit("设备已断开")
            except Exception as e:
                self.status_updated.emit(f"断开连接时出错: {str(e)}")
            finally:
                self.client = None
                self.is_connected = False
                self.connection_changed.emit(False)


class EEGAnalyzer(QThread):
    analysis_complete = Signal(str, str)

    def __init__(self, csv_path, api_key):
        super().__init__()
        self.csv_path = csv_path
        self.api_key = api_key
        self.report_path = os.path.splitext(self.csv_path)[0] + "_report.md"
        logger.info(f"分析器初始化，CSV路径: {csv_path}")

    def run(self):
        try:
            logger.info("开始分析数据...")
            df = self._load_and_preprocess_data()
            logger.info(f"成功加载数据，形状: {df.shape}")

            # 合并相关频段
            df['Alpha'] = df['Low Alpha'] + df['High Alpha']
            df['Beta'] = df['Low Beta'] + df['High Beta']
            df['Gamma'] = df['Low Gamma'] + df['Middle Gamma']

            report_content = self._generate_report(df)

            with open(self.report_path, "w", encoding="utf-8") as f:
                f.write(report_content)
            logger.info(f"报告已保存: {self.report_path}")

            self.analysis_complete.emit(report_content, self.report_path)

        except Exception as e:
            error_msg = f"分析失败: {str(e)}"
            logger.error(error_msg, exc_info=True)
            self.analysis_complete.emit(error_msg, "")

    def _load_and_preprocess_data(self):
        """增强版数据加载方法"""
        # 尝试多种编码
        encodings = ['utf-8', 'gbk', 'utf-8-sig', 'ISO-8859-1']
        df = None

        for encoding in encodings:
            try:
                df = pd.read_csv(self.csv_path, encoding=encoding)
                logger.info(f"使用编码 {encoding} 成功读取文件")

                # 检查必要列
                required_cols = ['时间', '信号质量', 'Delta', 'Theta',
                                 'Low Alpha', 'High Alpha', 'Low Beta',
                                 'High Beta', 'Low Gamma', 'Middle Gamma']

                missing_cols = [col for col in required_cols if col not in df.columns]

                if missing_cols:
                    logger.warning(f"缺失列: {', '.join(missing_cols)}")
                    # 尝试自动填充缺失列
                    for col in missing_cols:
                        if col == '信号质量':
                            df[col] = 200  # 默认良好信号
                        else:
                            df[col] = 0

                # 数据清洗
                df = df.dropna()
                if df.empty:
                    raise ValueError("数据为空，请检查文件内容")

                return df

            except Exception as e:
                logger.warning(f"编码 {encoding} 读取失败: {str(e)}")
                continue

        raise ValueError(f"无法读取CSV文件，尝试的编码: {encodings}")

    def _generate_report(self, df):
        """生成带格式的分析报告"""
        # 1. 基础统计
        stats = df.describe().to_markdown()

        # 2. 睡眠阶段分析
        sleep_analysis = self._analyze_sleep_stages(df)

        # 3. 可视化图表
        chart_path = self._generate_chart(df)

        # 4. AI分析
        ai_content = self._get_ai_analysis(df, stats, sleep_analysis)

        # 组装最终报告
        report_content = textwrap.dedent(f"""
        # <span style='color:#1890ff'>脑电睡眠分析报告</span>

        ## <span style='color:#13c2c2'>数据概览</span>
        - 记录时长: {len(df)} 个采样点
        - 开始时间: {df['时间'].iloc[0]}
        - 结束时间: {df['时间'].iloc[-1]}

        ```markdown
        {stats}
        ```

        ## <span style='color:#13c2c2'>睡眠阶段分析</span>
        {sleep_analysis}

        ## <span style='color:#13c2c2'>脑电波趋势</span>
        ![脑电波趋势图]({chart_path})

        ## <span style='color:#13c2c2'>AI健康评估</span>
        {ai_content}

        *报告生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*
        """)

        return report_content

    def _analyze_sleep_stages(self, df):
        """专业睡眠阶段分析"""
        # 计算各波段占比
        total_power = df[['Delta', 'Theta', 'Alpha', 'Beta', 'Gamma']].sum().sum()
        percentages = df[['Delta', 'Theta', 'Alpha', 'Beta', 'Gamma']].sum() / total_power * 100

        # 睡眠阶段判断
        sleep_stages = []
        if percentages.Delta > 40:
            sleep_stages.append("深睡期(Delta波主导)")
        if percentages.Theta > 30:
            sleep_stages.append("浅睡期(Theta波主导)")
        if percentages.Alpha > 20:
            sleep_stages.append("入睡期/REM(Alpha波活跃)")
        if percentages.Beta > 15:
            sleep_stages.append("清醒期(Beta波活跃)")

        # 睡眠质量评估
        sleep_score = min(100, int(
            0.4 * percentages.Delta +
            0.3 * percentages.Theta +
            0.2 * (100 - percentages.Beta) +
            0.1 * (100 - percentages.Gamma)
        ))

        quality = "优秀" if sleep_score >= 80 else "良好" if sleep_score >= 60 else "一般" if sleep_score >= 40 else "较差"

        analysis = textwrap.dedent(f"""
        ### 睡眠阶段分布
        - 深睡期(Delta): {percentages.Delta:.1f}%
        - 浅睡期(Theta): {percentages.Theta:.1f}% 
        - 入睡期/REM(Alpha): {percentages.Alpha:.1f}%
        - 清醒期(Beta): {percentages.Beta:.1f}%
        - 活跃期(Gamma): {percentages.Gamma:.1f}%

        ### 检测到的睡眠阶段
        {", ".join(sleep_stages) if sleep_stages else "无法确定明确睡眠阶段"}

        ### 睡眠质量评分
        <span style='color:{"#52c41a" if sleep_score >= 60 else "#fa8c16" if sleep_score >= 40 else "#f5222d"}'>
        {sleep_score}/100 ({quality})
        </span>
        """)

        return analysis

    def _generate_chart(self, df):
        """生成趋势图表"""
        plt.figure(figsize=(10, 6))

        # 平滑处理
        window_size = min(30, len(df) // 10)
        for wave in ['Delta', 'Theta', 'Alpha', 'Beta', 'Gamma']:
            if wave in df.columns:
                smoothed = df[wave].rolling(window=window_size).mean()
                plt.plot(df.index, smoothed, label=wave)

        plt.title('脑电波变化趋势 (平滑处理)')
        plt.xlabel('时间')
        plt.ylabel('幅值')
        plt.legend()
        plt.grid(True)

        chart_path = os.path.splitext(self.csv_path)[0] + "_chart.png"
        plt.savefig(chart_path, dpi=100, bbox_inches='tight')
        plt.close()

        return chart_path

    def _get_ai_analysis(self, df, stats, sleep_analysis):
        """获取AI分析内容"""
        if not self.api_key:
            return "## API密钥未配置\n\n无法获取AI分析结果"

        try:
            client = Ark(api_key=self.api_key)

            prompt = textwrap.dedent(f"""
            你是一个专业的睡眠脑电分析师。请根据以下数据提供专业分析：

            1. 基础统计:
            {stats}

            2. 睡眠阶段分析:
            {sleep_analysis}

            请用Markdown格式回答，包含以下部分：
            - 睡眠结构评估（各阶段占比是否正常）
            - 睡眠质量分析（基于脑电特征）
            - 潜在问题发现（如睡眠碎片化、深睡不足等） 
            - 科学建议（生活习惯、就医建议等）
            - 判断依据（解释分析逻辑）

            使用专业术语但保持易懂，重要数据用**加粗**强调。
            """)

            response = client.chat.completions.create(
                model="doubao-1.5-thinking-pro-250415",
                messages=[{"role": "user", "content": prompt}],
                temperature=0.3  # 降低随机性
            )

            return response.choices[0].message.content

        except Exception as e:
            logger.error(f"AI分析失败: {str(e)}")
            return f"## AI分析失败\n\n错误信息: {str(e)}\n\n请检查网络连接和API密钥"


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("基于EEG与人工智能的睡眠健康评估系统 V1.0")
        self.setGeometry(100, 100, 1024, 768)
        self.collector = TGAMDataCollector()
        self.analyzer = None
        self.api_key = None
        self._setup_ui()
        self._connect_signals()

        # 添加状态栏
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.statusBar.setStyleSheet("""
            QStatusBar {
                background-color: #f5f5f5;
                border-top: 1px solid #ddd;
                padding: 2px;
            }
        """)

    def _setup_ui(self):
        """初始化界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        layout.setContentsMargins(10, 10, 10, 10)
        layout.setSpacing(10)

        # ===== 标题栏 =====
        title_label = QLabel("基于EEG与人工智能的睡眠健康评估系统 V1.0")

        # 修复弃用的字体数据库用法
        font_db = QFontDatabase()
        all_fonts = font_db.families()

        title_font = QFont()
        font_families = ["Microsoft YaHei", "SimHei", "Arial"]
        for font in font_families:
            if font in all_fonts:
                title_font.setFamily(font)
                break
        title_font.setPointSize(16)
        title_font.setBold(True)
        title_label.setFont(title_font)

        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            QLabel {
                color: #2c3e50;
                padding: 10px;
                background-color: #e6f7ff;
                border-radius: 5px;
            }
        """)
        layout.addWidget(title_label)

        # ===== 控制面板 =====
        control_panel = QWidget()
        control_layout = QHBoxLayout(control_panel)
        control_layout.setContentsMargins(0, 0, 0, 0)
        control_layout.setSpacing(15)

        # 创建样式统一的按钮
        self.btn_connect = self._create_button("连接设备", "#1890ff")
        self.btn_record = self._create_button("开始记录", "#52c41a")
        self.btn_analyze = self._create_button("分析数据", "#722ed1")

        self.btn_record.setEnabled(False)
        self.btn_analyze.setEnabled(False)

        control_layout.addWidget(self.btn_connect)
        control_layout.addWidget(self.btn_record)
        control_layout.addWidget(self.btn_analyze)
        control_layout.addStretch()

        layout.addWidget(control_panel)

        # ===== 数据展示区 =====
        self.tabs = QTabWidget()
        self.tabs.setStyleSheet("""
            QTabWidget::pane {
                border: 1px solid #d9d9d9;
                border-radius: 4px;
            }
            QTabBar::tab {
                background: #f0f0f0;
                padding: 8px 12px;
                border: 1px solid #d9d9d9;
                border-bottom: none;
                border-top-left-radius: 4px;
                border-top-right-radius: 4px;
            }
            QTabBar::tab:selected {
                background: white;
                border-bottom: 2px solid #1890ff;
            }
        """)

        # 实时数据标签页
        self.raw_view = QTextEdit()
        self.raw_view.setReadOnly(True)
        self.raw_view.setFont(QFont("Consolas", 10))
        self.raw_view.setStyleSheet("""
            QTextEdit {
                background-color: #fafafa;
                border: none;
                padding: 5px;
            }
        """)

        # 分析报告标签页
        self.report_view = QTextEdit()
        self.report_view.setReadOnly(True)

        # 修复弃用的字体数据库用法
        report_font = QFont()
        font_families = ["Microsoft YaHei", "SimHei", "Arial"]
        for font in font_families:
            if font in all_fonts:  # 使用前面创建的 all_fonts
                report_font.setFamily(font)
                break
        report_font.setPointSize(11)
        self.report_view.setFont(report_font)

        self.report_view.setStyleSheet("""
            QTextEdit {
                background-color: #fafafa;
                border: none;
                padding: 10px;
            }
        """)

        # 系统日志标签页
        self.log_view = QTextEdit()
        self.log_view.setReadOnly(True)
        self.log_view.setFont(QFont("Consolas", 9))
        self.log_view.setStyleSheet("""
            QTextEdit {
                background-color: #fafafa;
                border: none;
                padding: 5px;
            }
        """)

        self.tabs.addTab(self.raw_view, "实时数据")
        self.tabs.addTab(self.report_view, "分析报告")
        self.tabs.addTab(self.log_view, "系统日志")

        layout.addWidget(self.tabs, 1)

        # 重定向日志到视图
        class LogHandler(logging.Handler):
            def __init__(self, text_widget):
                super().__init__()
                self.text_widget = text_widget

            def emit(self, record):
                msg = self.format(record)
                self.text_widget.append(msg)

        log_handler = LogHandler(self.log_view)
        log_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
        logger.addHandler(log_handler)

    def _create_button(self, text, color):
        """创建样式统一的按钮"""
        button = QPushButton(text)
        button.setFont(QFont("Microsoft YaHei", 10))
        button.setStyleSheet(f"""
            QPushButton {{
                background-color: {color};
                color: white;
                border: none;
                border-radius: 4px;
                padding: 8px 16px;
                min-width: 100px;
            }}
            QPushButton:hover {{
                background-color: qlineargradient(
                    x1:0, y1:0, x2:0, y2:1,
                    stop:0 {color}, stop:1 #003a8c
                );
            }}
            QPushButton:pressed {{
                background-color: #003a8c;
            }}
            QPushButton:disabled {{
                background-color: #d9d9d9;
                color: #8c8c8c;
            }}
        """)
        return button

    def update_status(self, status):
        """更新状态显示"""
        self.statusBar.showMessage(status)

    def _connect_signals(self):
        """连接信号槽"""
        self.btn_connect.clicked.connect(lambda: asyncio.create_task(self.toggle_connection()))
        self.btn_record.clicked.connect(self.toggle_recording)
        self.btn_analyze.clicked.connect(self.analyze_data)
        self.collector.data_received.connect(self.update_raw_view)
        self.collector.file_created.connect(self.enable_analysis)
        self.collector.status_updated.connect(self.update_status)
        self.collector.connection_changed.connect(self.update_connection_status)
        self.collector.recording_changed.connect(self.update_recording_status)

    def update_connection_status(self, connected):
        """更新连接状态"""
        if connected:
            self.btn_connect.setText("断开连接")
            self.btn_connect.setStyleSheet("""
                QPushButton {
                    background-color: #f5222d;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 8px 16px;
                }
            """)
            self.btn_record.setEnabled(True)
        else:
            self.btn_connect.setText("连接设备")
            self.btn_connect.setStyleSheet("""
                QPushButton {
                    background-color: #1890ff;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 8px 16px;
                }
            """)
            self.btn_record.setEnabled(False)
            if self.collector.is_recording:
                self.collector.stop_recording()
                self.btn_record.setText("开始记录")

    def update_recording_status(self, recording):
        """更新记录状态"""
        if recording:
            self.btn_record.setText("停止记录")
            self.btn_record.setStyleSheet("""
                QPushButton {
                    background-color: #f5222d;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 8px 16px;
                }
            """)
        else:
            self.btn_record.setText("开始记录")
            self.btn_record.setStyleSheet("""
                QPushButton {
                    background-color: #52c41a;
                    color: white;
                    border: none;
                    border-radius: 4px;
                    padding: 8px 16px;
                }
            """)

    async def toggle_connection(self):
        """切换连接状态"""
        if self.collector.is_connected:  # 使用 is_connected 而不是 connected
            await self.collector.disconnect_device()
        else:
            await self.collector.connect_device()

    def toggle_recording(self):
        """切换记录状态"""
        if not self.collector.is_recording:
            path = QFileDialog.getSaveFileName(self, "保存文件", "", "CSV文件 (*.csv)")[0]
            if path:
                if not path.endswith('.csv'):
                    path += '.csv'
                base_path = os.path.splitext(path)[0]
                if self.collector.start_recording(base_path):
                    self.raw_view.clear()
        else:
            self.collector.stop_recording()

    def analyze_data(self):
        """启动分析"""
        if not self.collector.is_recording:
            path = QFileDialog.getOpenFileName(self, "选择CSV文件", "", "CSV文件 (*.csv)")[0]
        else:
            path = self.collector.current_csv_path

        if path:
            self.start_analysis(path)

    def start_analysis(self, path):
        """开始分析指定路径的文件"""
        self.report_view.clear()
        self.report_view.setText("正在分析数据，请稍候...")
        self.tabs.setCurrentIndex(1)  # 切换到报告标签页

        self.analyzer = EEGAnalyzer(path, self.api_key)
        self.analyzer.analysis_complete.connect(self.show_report)
        self.analyzer.start()

    def show_report(self, report_content, report_path):
        """显示分析报告"""
        self.report_view.setMarkdown(report_content)
        self.statusBar.showMessage(f"报告已生成: {report_path}")

    def enable_analysis(self, path):
        """启用分析功能"""
        self.btn_analyze.setEnabled(True)
        self.collector.current_csv_path = path

    def closeEvent(self, event):
        """关闭应用程序前的清理"""
        if self.collector.is_recording:
            self.collector.stop_recording()

        if self.analyzer and self.analyzer.isRunning():
            self.analyzer.quit()
            self.analyzer.wait(2000)

        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create("Fusion"))

    # 设置应用字体
    app_font = QFont("Microsoft YaHei", 10)
    app.setFont(app_font)

    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)

    window = MainWindow()
    window.show()

    with loop:
        loop.run_forever()